package repository

import (
	v1 "SmartLibrary/api/v1"
	"SmartLibrary/internal/model"
	"context"
	"errors"
	"gorm.io/gorm"
)

type UserRepository interface {
	GetByID(ctx context.Context, id int64) (*model.User, error)
	GetByUsername(ctx context.Context, username string) (*model.User, error)
	GetBookByPage(ctx context.Context, con *v1.GetBooksCondition) (*[]model.UserBook, error)
	GetBookAll(ctx context.Context) (int64, error)
	GetBookAllByCondition(ctx context.Context, con *v1.GetBooksCondition) (int64, error)
	GetBookByCondition(ctx context.Context, con *v1.GetBooksCondition) (*[]model.UserBook, error)
	GetBookRules(ctx context.Context) (*[]model.UserRule, error)
	GetNotices(ctx context.Context) (*[]model.UserNotice, error)
	GetCardNumberByUserId(ctx context.Context, userId int64) (int64, error)
	GetBorrowByCardNumber(ctx context.Context, con *v1.GetBorAndVioCondition) (*[]model.UserBorrow, error)
	GetBorrowAll(ctx context.Context, cardNumber int64) (int64, error)
	GetBorrowAllByCondition(ctx context.Context, con *v1.GetBorAndVioCondition) (int64, error)
	GetBorrowByCondition(ctx context.Context, con *v1.GetBorAndVioCondition) (*[]model.UserBorrow, error)
	GetViolationsByCardNumber(ctx context.Context, con *v1.GetBorAndVioCondition) (*[]model.UserViolation, error)
	GetViolationAll(ctx context.Context, cardNumber int64) (int64, error)
	GetViolationAllByCondition(ctx context.Context, con *v1.GetBorAndVioCondition) (int64, error)
	GetViolationByCondition(ctx context.Context, con *v1.GetBorAndVioCondition) (*[]model.UserViolation, error)
	GetComments(ctx context.Context) (*[]model.UserComment, error)
	CreateComment(ctx context.Context, req *v1.CreateCommentRequest) error
}

func NewUserRepository(
	r *Repository,
) UserRepository {
	return &userRepository{
		Repository: r,
	}
}

type userRepository struct {
	*Repository
}

func (r *userRepository) GetByID(ctx context.Context, userId int64) (*model.User, error) {
	var user model.User
	if err := r.DB(ctx).Where("user_id = ?", userId).First(&user).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, v1.ErrUserIdNotFound
		}
		return nil, err
	}
	return &user, nil
}

func (r *userRepository) GetByUsername(ctx context.Context, username string) (*model.User, error) {
	var user model.User
	if err := r.DB(ctx).Where("username = ?", username).First(&user).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, v1.ErrUsernameOrPasswordWrong
		}
		return nil, err
	}
	return &user, nil
}

func (r *userRepository) GetBookByPage(ctx context.Context, con *v1.GetBooksCondition) (*[]model.UserBook, error) {
	var books []model.UserBook
	return &books, r.DB(ctx).Offset(con.Offset).Limit(con.Limit).Find(&books).Error
}

func (r *userRepository) GetBookAll(ctx context.Context) (int64, error) {
	var count int64
	return count, r.DB(ctx).Model(&model.UserBook{}).Select("book_id").Count(&count).Error
}

func (r *userRepository) GetBookAllByCondition(ctx context.Context, con *v1.GetBooksCondition) (int64, error) {
	var count int64
	return count, r.DB(ctx).Model(&model.UserBook{}).
		Select("book_id").Where(con.Query+" LIKE ?", "%"+con.Condition+"%").
		Count(&count).Error
}
func (r *userRepository) GetBookByCondition(ctx context.Context, con *v1.GetBooksCondition) (*[]model.UserBook, error) {
	var books []model.UserBook
	return &books, r.DB(ctx).
		Where(con.Query+" LIKE ?", "%"+con.Condition+"%").
		Offset(con.Offset).Limit(con.Limit).Find(&books).Error
}

func (r *userRepository) GetBookRules(ctx context.Context) (*[]model.UserRule, error) {
	var rules []model.UserRule
	return &rules, r.DB(ctx).Find(&rules).Error
}

func (r *userRepository) GetNotices(ctx context.Context) (*[]model.UserNotice, error) {
	var notices []model.UserNotice
	return &notices, r.DB(ctx).Find(&notices).Error
}

func (r *userRepository) GetCardNumberByUserId(ctx context.Context, userId int64) (int64, error) {
	var cardNumber int64
	return cardNumber, r.DB(ctx).Model(&model.User{}).
		Select("card_number").Where("user_id = ?", userId).
		First(&cardNumber).Error
}

func (r *userRepository) GetBorrowByCardNumber(ctx context.Context, con *v1.GetBorAndVioCondition) (*[]model.UserBorrow, error) {
	var borrows []model.UserBorrow
	return &borrows, r.DB(ctx).
		Where("card_number = ?", con.CardNumber).
		Offset(con.Offset).Limit(con.Limit).Find(&borrows).Error
}

func (r *userRepository) GetBorrowAll(ctx context.Context, cardNumber int64) (int64, error) {
	var count int64
	return count, r.DB(ctx).Model(&model.UserBorrow{}).
		Select("borrow_id").
		Where("card_number = ?", cardNumber).Count(&count).Error
}

func (r *userRepository) GetBorrowAllByCondition(ctx context.Context, con *v1.GetBorAndVioCondition) (int64, error) {
	var count int64
	return count, r.DB(ctx).Model(&model.UserBorrow{}).
		Select("borrow_id").
		Where("card_number = ? AND "+con.Query+" LIKE ?", con.CardNumber, "%"+con.Condition+"%").
		Count(&count).Error
}

func (r *userRepository) GetBorrowByCondition(ctx context.Context, con *v1.GetBorAndVioCondition) (*[]model.UserBorrow, error) {
	var borrows []model.UserBorrow
	return &borrows, r.DB(ctx).
		Where("card_number = ? AND "+con.Query+" LIKE ?", con.CardNumber, "%"+con.Condition+"%").
		Offset(con.Offset).Limit(con.Limit).
		Find(&borrows).Error
}

func (r *userRepository) GetViolationsByCardNumber(ctx context.Context, con *v1.GetBorAndVioCondition) (*[]model.UserViolation, error) {
	var violations []model.UserViolation
	return &violations, r.DB(ctx).
		Where("card_number = ?", con.CardNumber).
		Offset(con.Offset).Limit(con.Limit).
		Find(&violations).Error
}

func (r *userRepository) GetViolationAll(ctx context.Context, cardNumber int64) (int64, error) {
	var count int64
	return count, r.DB(ctx).Model(&model.UserViolation{}).
		Select("violation_id").
		Where("card_number = ?", cardNumber).Count(&count).Error
}

func (r *userRepository) GetViolationAllByCondition(ctx context.Context, con *v1.GetBorAndVioCondition) (int64, error) {
	var count int64
	return count, r.DB(ctx).Model(&model.UserViolation{}).
		Select("violation_id").
		Where("card_number = ? AND "+con.Query+" LIKE ?", con.CardNumber, "%"+con.Condition+"%").
		Count(&count).Error
}

func (r *userRepository) GetViolationByCondition(ctx context.Context, con *v1.GetBorAndVioCondition) (*[]model.UserViolation, error) {
	var violations []model.UserViolation
	return &violations, r.DB(ctx).
		Where("card_number = ? AND "+con.Query+" LIKE ?", con.CardNumber, "%"+con.Condition+"%").
		Offset(con.Offset).Limit(con.Limit).
		Find(&violations).Error
}

func (r *userRepository) GetComments(ctx context.Context) (*[]model.UserComment, error) {
	var comments []model.UserComment
	return &comments, r.DB(ctx).Find(&comments).Error
}

func (r *userRepository) CreateComment(ctx context.Context, req *v1.CreateCommentRequest) error {
	var comment model.UserComment
	comment.UserID = req.UserId
	comment.CommentMessage = req.CommentMessage
	comment.CreateTime = req.CreateTime

	return r.DB(ctx).Create(&comment).Error
}
